-- login
-- Create by panyl
-- 压力测试-登录

module("login", package.seeall);

require "socket"

local users = {};

local createdAccount;
local onTakeServerTicket;
local update;

local account_prefix;
local user_count;

local init_account = true;

local state = {};

function init()
    init_account = true;

    state = {};

    -- 注册相关消息触发器
    registerTriggers();

    -- -- 定时循环处理登录
    cc.Director:getInstance():getScheduler():scheduleScriptFunc(update, 1, false);
end

update = function()
    if init_account then
        -- 正在创建账号
        cclog("正在创建账号...");
        createAccount(account_prefix, user_count);
        return;
    end

    -- 遍历所有的代理
    local total = 0;
    local loginOK = 0;
    local relogin = 0;
    local curTime = os.time();
    for _, user in pairs(users) do
        total = total + 1;
        repeat
            if user.loginOK then
                loginOK = loginOK + 1;
                break;
            end

            -- 如果登录aaa失败
            if user.state == "aaa_fail" then
                relogin = relogin + 1;

                -- 重新账密登录
                loginAAA(user);
                break;
            end

            if not user.reloginTime then
                break;
            end

            if curTime < user.reloginTime then
                -- 10秒内不做重新登录
                break;
            end

            -- if user.loginTo == "AAA" then
            --     break;
            -- end

            relogin = relogin + 1;

            -- 重新账密登录
            loginAAA(user);
        until true;
    end

    cclog(string.format("总共登录%d个玩家，%d个玩家登录成功，%d个玩家正在重新登录。", total, loginOK, relogin));
end

function start(account, count)
    if DEBUG_MODE ~= 1 or STRESS_TEST ~= 1 then
        return;
    end

    -- 先创建账号，成功之后自动登录
    createAccount(account, count);

    -- 创建玩家代理
    account_prefix = account;
    user_count = count;
    -- batchCreateUser(account, count);
end

function startLogin(count)
    trace("login", "**** 开始进行登录压力测试，登录%d个玩家", count);

    batchCreateUser(account_prefix, count);

    -- AAA验证
    local try_list = {};
    for _, user in pairs(users) do
        repeat
            cclog(user.userName .. "正在登录AAA...");
        until loginAAA(user);
    end
end

function batchCreateUser(account, count)
    trace("login", "**** 开始创建压力测试账户");

    users = {};

    -- 先创建账号
    for i = 1, count do
        createUser(account .. i);
    end
end

function createUser(account)
    local user = Agent.new(account, AAA_IP, AAA_PORT);
    users[account] = user;

    CommandM.addAgent(user);
end

function createAccount(account, count)
    init_account = true;

    -- 到AAA验证连接
    local v = {
        ["server_type"]     = CFG_TYPE_CONSOLE,
        ["server_name"]     = account,
        ["request_service"] = CFG_AAA_LOGIN,
        ["cookie"]          = math.random(10000),
    };

    -- 临时代理
    local agent = Agent.new("create_account", AAA_IP, AAA_PORT);
    CommandM.addAgent(agent);
    agent:sendCmd("CMD_INTERNAL_AUTH", v);

    -- 创建count个账号
    -- 发消息创建账号
    v = {
        ["account"] = "qc_" .. account,     -- 需要加上qc_
        ["password"] = "000000",
        ["info"] = {
                ["privilege"] = 1000,
            },
        ["count"] = count,
    };
    agent:sendCmd("CMD_BATCH_CREATE_ACCOUNT", v);
end

-- 创建账号消息回调
createdAccount = function(agent, args)
    local result = args.result;

    cclog(args.msg);

    if not init_account then
        return;
    end

    -- 创建完了
    init_account = false;

    -- batchCreateUser(account, count);

    startLogin(args.cookie)
end

function loginAAA(user)
    -- 创建成功
    -- 验证
    -- 发送登录指令
    trace("stress login", user.userName .. "发送登录AAA消息");

    if not user:connect(AAA_IP, AAA_PORT) then
        trace("stress login", user.userName .. "连接AAA失败。");
        return false;
    end

    user.reloginTime = os.time() + 20;

    user.loginTo = "AAA";

    -- 标记为正在登录AAA
    user.state = "login_aaa";

    -- 到AAA验证连接
    local v = {
        ["server_type"]     = CFG_TYPE_CONSOLE,
        ["server_name"]     = user.userName,
        ["request_service"] = CFG_AAA_LOGIN,
        ["cookie"]          = math.random(10000),
    };
    user:sendCmd("CMD_INTERNAL_AUTH", v);

    v = {
        ["account"]  = user.userName,
        ["password"] = "000000",
        ["version"]  = SUPPORT_VERSION,
        ["extra_para"] = {},
    }
    user:sendCmd("CMD_AUTH_ACCOUNT", v);

    return true;
end

onTakeServerTicket = function(user, args)
    if args.result ~= 1 then
        return;
    end

    -- 如果还未有服务器信息
    local serverInfo = user.serverInfo;
    if not serverInfo or not serverInfo["ip"] or not serverInfo["port"] then
        return;
    end

    serverInfo = table.add(serverInfo, {
        ["seed"] = args.seed,
        ["privilege"] = args.privilege,
        ["server_name"] = args.server_name,
    });
    user.serverInfo = serverInfo;

    repeat
        cclog(user.userName .. "正在登录GS。");
    until loginGS(user);
end

function loginGS(user)
    -- 如果还未有服务器信息
    local serverInfo = user.serverInfo;
    if not serverInfo or not serverInfo["ip"] or not serverInfo["port"] then
        return true;
    end

    -- 开始登录服务器
    if not user:connect(serverInfo["ip"], serverInfo["port"]) then
        cclog(user.userName .. "连接gs失败。");
        return false;
    end

    user.reloginTime = os.time() + 30;

    user.loginTo = "GS";

    -- 标记为正在登录gs
    user.state = "login_gs";

    -- 到AAA验证连接
    local v = {
        ["server_type"]     = CFG_TYPE_CONSOLE,
        ["server_name"]     = user.userName,
        ["request_service"] = CFG_SERVICE_GS,
        ["cookie"]          = math.random(10000),
    };
    user:sendCmd("CMD_INTERNAL_AUTH", v);

    -- 发送登录指令
    trace("stress_login", user.userName .. "发送登录GS消息");

    v = {
        ["account"]  = user.userName,
        ["auth_key"] = serverInfo.auth_key,
        ["seed"]     = serverInfo.seed,
    };

    user:sendCmd("CMD_LOGIN", v);

    return true;
end

-- 注册相关消息触发器
function registerTriggers()
    -- 消息触发器-创建账号结果
    CommandM.registerMsgTrigger("stress_login", "MSG_OPERATE_ACCOUNT_RESULT", createdAccount);

    -- 等待消息到达
    CommandM.registerMsgTrigger("stress_login", "MSG_AUTH_ACCOUNT_RESULT", function(user, para)
        -- 2秒内不能再次登录
        user.reloginTime = os.time() + 2;
        user.loginTo = nil;

        if para.result ~= LOGIN_AUTH_OK then
            -- TODO: 验证未成功，断开连接
            trace("stress_login", "验证失败");
            user:disconnect();

            -- 标记为正在登录AAA失败
            user.state = "aaa_fail";

            return
        end

        -- 标记为正在登录AAA成功
        user.state = "aaa_ok";

        user.serverInfo = {
            ["auth_key"] = para.auth_key,
        }

        -- 获取服务器
        trace("stress_login", "获取服务器");
        user:sendCmd("CMD_L_GET_SERVER_LIST", {});
    end);

    -- 等待服务器列表
    CommandM.registerMsgTrigger("stress_login", "MSG_L_SERVER_LIST", function(user, args)
        local info = args.server_list[1];

        -- 记录下服务器信息
        user.serverInfo["ip"] = info.ip;
        user.serverInfo["port"] = info.port;
    end);

    -- 等待服务器门票
    CommandM.registerMsgTrigger("stress_login", "MSG_TAKE_SERVER_TICKET_RESULT", onTakeServerTicket);

    -- 登录消息回调
    CommandM.registerMsgTrigger("stress_login", "MSG_LOGIN", function(user, args)
        -- 标记为登录gs成功
        user.state = "gs_ok";

        user:sendCmd("CMD_SELECT_LOGIN_THREAD", { ["thread"] = 1 });
    end);

    -- 完全登录成功了
    CommandM.registerMsgTrigger("stress_login", "MSG_LOGIN_NOTIFY_OK", function(user, args)
        user.loginOK = true;
    end);

    -- 角色信息
    CommandM.registerMsgTrigger("stress_login", "MSG_EXISTED_CHAR_LIST", function(user, args)
        local arr = args.user_info;
        if (#arr == 0) then
            cclog("目前没有任何角色");

            -- TODO: 临时方案 - 自行创建下了
            user:sendCmd("CMD_CREATE_NEW_CHAR", {
                char_name   = user.userName,
                gender      = 1,
                extra_info  = { client_os = getPlatformName() },
            });
        else
            -- 选择角色并登录
            local info = arr[1];
            user:sendCmd("CMD_LOAD_EXISTED_CHAR", {
                char_rid   = info.rid,
                extra_info = { client_os = getPlatformName() },
            });
        end
    end);
end
